package com.boot2.core.dao.mybatis;

import com.boot2.core.dao.exception.DAOException;
import com.boot2.core.dao.exception.UnsupportDataTypeException;
import com.boot2.core.utils.PrivateMethodUtil;
import com.boot2.core.utils.ReflectUtil;
import com.boot2.core.utils.StringUtils;
import lombok.extern.apachecommons.CommonsLog;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.beans.IntrospectionException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 基于Mybatis的基础泛型DAO实现类。
 *
 * @param <T> 业务实体类型
 *            ID类型 ，如：String、Long、Integer 等
 * @author 张伟林
 */
// @Repository //不能在此处创建实例，否则拿不到泛型class
// public class MyIbatisBaseDao<T>
// extends SqlSessionDaoSupport // implements IBaseGenericDAO<T>
// public class MyIbatisBaseDao<T> extends SqlSessionTemplateDaoSupport //
// implements IBaseGenericDAO<T>
@SuppressWarnings("rawtypes")
@CommonsLog
public class MyBatisBaseDao<T> extends SqlSessionTemplateDaoSupport implements IBaseGenericDAO<T> {
//    protected Object self;
    protected Class<T> clazz ;

    /**
     * SqlMapping命名空间
     */
    private String sqlNamespace;

    private static final String SQLNAME_SEPARATOR = ".";

    // ===以下要定义成枚举====
    public static enum CrudSqlId {
        insert("增加"),
        deleteById("按id删除"),
        deleteByIds("按id批量删除"),
        updateSelectedColumnById("按id更新指定字段"),
        updateAllColumnById("按id更新所有字段"),
        update("更新实体"),
        selectListBySelected("按对象条件查列表"),
        selectById("按id查单个"),
        selectByIds("按多个id查"),
        selectAll("查询所有");
        private String cnName;

        CrudSqlId(String name) {
            this.cnName = name;
        }

        public String getCode() {
            return this.name();
        }

        public String getCnName() {
            return this.cnName;
        }

        public static String getCnName(String code) {
            for (CrudSqlId item : CrudSqlId.values()) {
                if (item.getCode().equals(code)) {
                    return item.getCnName();
                }
            }
            return code;
        }

        @Override
        public String toString() {
            return "code:" + this.name() + ",cnName:" + this.cnName;
        }
    }

    /**
     * 不能用于SQL中的非法字符（主要用于排序字段名）
     */
    public static final String[] ILLEGAL_CHARS_FOR_SQL = {",", ";", " ", "\"", "%"};

    @SuppressWarnings("unchecked")
    public MyBatisBaseDao() {
        clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        sqlNamespace = clazz.getName();
        ExtendsProperties extendsProperties = clazz.getAnnotation(ExtendsProperties.class);
        Field[] fieldArr = null;
        if (null == extendsProperties || !extendsProperties.value()) {
            fieldArr = ReflectUtil.getAllFieldList(clazz);
        } else {
//            fieldArr = ReflectUtil.getAllFieldList2(clazz).toArray(new Field[0]);
            fieldArr = ReflectUtil.getAllFieldWithParentNotTransientList(clazz).toArray(new Field[0]);
        }

        Method method = null;
        try {
            method = MyBatisDaoUtil.class.getDeclaredMethod("putFieldCache", String.class, Field[].class);
            method.setAccessible(true);
            method.invoke(null, sqlNamespace, fieldArr);
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
//            e.printStackTrace();
            throw new DAOException("mybatis dao 初始化失败",e);
        }
        log.info("通过范型加载sqlNamespace: " + sqlNamespace);
    }

    /**
     * 将SqlMapping命名空间与给定的SqlMapping名组合在一起。
     *
     * @param sqlId SqlMapping名
     * @return 组合了SqlMapping命名空间后的完整SqlMapping名
     * @throws UnsupportDataTypeException
     * @throws IntrospectionException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public String getSqlId(Object obj, String sqlId) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, IntrospectionException, UnsupportDataTypeException {
        setMyBatisType(obj, sqlId);
        String sqlId2 = sqlNamespace + SQLNAME_SEPARATOR + sqlId;
        loger.info("执行sqlId: " + sqlId2);
        return sqlId2;
    }

    /**
     * 获取SqlMapping命名空间
     *
     * @return SqlMapping命名空间
     */
    public String getSqlNamespace() {
        return sqlNamespace;
    }

    /**
     * 设置SqlMapping命名空间。 此方法只用于注入SqlMapping命名空间，以改变默认的SqlMapping命名空间，
     * 不能滥用此方法随意改变SqlMapping命名空间。
     *
     * @param sqlNamespace SqlMapping命名空间
     */
    public void setSqlNamespace(String sqlNamespace) {
        this.sqlNamespace = sqlNamespace;
    }

    // /**
    // * 生成主键值。 默认情况下什么也不做； 如果需要生成主键，需要由子类重写此方法根据需要的方式生成主键值。
    // *
    // * @param ob
    // * 要持久化的对象
    // */
    // public void generateId(T ob)
    // {
    //
    // }

    /**
     * @param obj   如果该对象被设置过自动sql,即isAutoSql,则生成上下文环境
     * @param sqlId 如果sqlId是内置的几个，则生成上下文环境
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws IntrospectionException
     * @throws UnsupportDataTypeException
     */
    private void setMyBatisType(Object obj, String sqlId) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, IntrospectionException, UnsupportDataTypeException {
        if (null == obj) {
            return;
        }
        // 如果是内置的sqlId,那么则设置上下文，即jdbcType等
        CrudSqlId[] crudSqlId = CrudSqlId.values();
        for (CrudSqlId crudType : crudSqlId) {
            if (sqlId.equals(crudType.name())) {
                // 拿到所有的
                MyBatisDaoUtil.setMyBatisType(obj);
                return;
            }
        }
        // 是否自动sql
        if (MyBatisSupportEntity.class.isAssignableFrom(obj.getClass())) {
            Boolean isAutoSql = (Boolean) PrivateMethodUtil.invokeMethod(obj, "getIsAutoSql");
            if (null != isAutoSql && isAutoSql) {
                // 拿到所有的
                MyBatisDaoUtil.setMyBatisType(obj);
            }
        }

    }

    @Override
    public int save(T t) {
        try {

            return sqlSession.insert(getSqlId(t, CrudSqlId.insert.name()), t);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("添加失败:t: " + t, e);
        }
    }

    @Override
    public int insert(T t) {
        return save(t);
    }

    @Override
    public Integer updateSelectedColumn(T t) {
        try {
            // MyBatisDaoUtil.setMyBatisType(t);
            setPkName(t);
            return sqlSession.update(getSqlId(t, CrudSqlId.updateSelectedColumnById.name()), t);
        } catch (Exception e) {
//            //e.printStackTrace();
            throw new DAOException("更新失败:t: " + t, e);
        }
    }

    @Override
    public Integer updateAllColumn(T t) {
        try {
            // MyBatisDaoUtil.setMyBatisType(t);
            setPkName(t);
            return sqlSession.update(getSqlId(t, CrudSqlId.updateAllColumnById.name()), t);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("更新失败:t: " + t, e);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public T selectById(Serializable id) {
        try {
            T t = clazz.newInstance();
            setPkValue(id, t);
            return (T) sqlSession.selectOne(getSqlId(t, CrudSqlId.selectById.name()), t);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:id: " + id, e);
        }
    }

    private void setPkValue(Serializable id, T t) throws IllegalAccessException {
        Field[] fieldArr = FieldUtils.getFieldsWithAnnotation(clazz, Pk.class);
        if (null != fieldArr && fieldArr.length > 0) {
            FieldUtils.writeField(fieldArr[0], t, id, true);
            PrivateMethodUtil.invokeMethod(t, "setPkColumnName", MyBatisDaoUtil.getStandardColumnName(fieldArr[0].getName()));
            PrivateMethodUtil.invokeMethod(t, "setPkName", fieldArr[0].getName());
        } else {
            loger.warn("找不到主键字段:" + clazz);
            // throw new DAOException("找不到主键字段:" + clazz);
        }
    }

    private void setPkName(T t) throws IllegalAccessException {
        Field[] fieldArr = FieldUtils.getFieldsWithAnnotation(clazz, Pk.class);
        if (null != fieldArr && fieldArr.length > 0) {
            PrivateMethodUtil.invokeMethod(t, "setPkColumnName", MyBatisDaoUtil.getStandardColumnName(fieldArr[0].getName()));
            PrivateMethodUtil.invokeMethod(t, "setPkName", fieldArr[0].getName());
        } else {
            // throw new DAOException("找不到主键字段:" + clazz);
            loger.warn("找不到主键字段:" + clazz);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> selectByIds(Serializable... ids) {
        try {
            // 这种方式 不行，求解
            // T entity=clazz.newInstance();
            // MethodUtils.invokeMethod(entity, "setIds", ids);
            MyBatisSupportEntity entity = (MyBatisSupportEntity) clazz.newInstance();
            // entity.setIds(ids);
            PrivateMethodUtil.invokeMethod(entity, "setIds", Arrays.asList(ids));
            // TODO 等改善setIds写死情况
            return sqlSession.selectList(getSqlId(entity, CrudSqlId.selectByIds.name()), entity);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:id: " + StringUtils.join(ids, ","), e);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> selectListBySelected(T t) {
        try {
            List<E> list = sqlSession.selectList(getSqlId(t, CrudSqlId.selectListBySelected.name()), t);
            return list;
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | IntrospectionException | UnsupportDataTypeException e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:id: t" + t, e);
        }
    }

    /*
     * (non-Javadoc)
     * @see com.harmony.framework.dao.mybatis.IBaseGenericDAO#deleteByIds(
     * Serializable [])
     */
    @Override
    public Integer deleteByIds(Serializable... ids) {
        try {
            return sqlSession.delete(getSqlId(null, CrudSqlId.deleteByIds.name()), ids);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:id: " + StringUtils.join(ids, ","), e);
        }
    }

    @Override
    public Integer deleteById(Serializable id) {
        try {
            T t = clazz.newInstance();
            setPkValue(id, t);
            // return sqlSession.delete(getSqlId(null,
            // CrudSqlId.deleteById.name()), id);
            return sqlSession.delete(getSqlId(null, CrudSqlId.deleteById.name()), id);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:id: " + id, e);
        }
    }

    /**
     * 对
     * {@link org.apache.ibatis.session.SqlSession#insert(java.lang.String, java.lang.Object)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId    映射的语句ID
     * @param paramObj 参数
     * @return 执行结果——插入成功的记录数
     * @see org.apache.ibatis.session.SqlSession#insert(java.lang.String,
     * java.lang.Object)
     */
    @Override
    public int insert(String sqlId, Object paramObj) {
        try {
            return sqlSession.insert(getSqlId(paramObj, sqlId), paramObj);
        } catch (Exception e) {
//            //e.printStackTrace();
            throw new DAOException("添加失败:sqlId: " + sqlId + " , paramObj: " + paramObj, e);
        }
    }

    /**
     * 对{@link org.apache.ibatis.session.SqlSession#insert(java.lang.String)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId 映射的语句ID
     * @return 执行结果——插入成功的记录数
     * @see org.apache.ibatis.session.SqlSession#insert(java.lang.String)
     */
    @Override
    public int insert(String sqlId) {
        try {
            return sqlSession.insert(getSqlId(null, sqlId));
        } catch (Exception e) {
//            //e.printStackTrace();
            throw new DAOException("添加失败:sqlId: " + sqlId, e);
        }
    }

    /**
     * 对
     * {@link org.apache.ibatis.session.SqlSession#update(java.lang.String, java.lang.Object)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId    映射的语句ID
     * @param paramObj 参数
     * @return 执行结果——更新成功的记录数
     * @see org.apache.ibatis.session.SqlSession#update(java.lang.String,
     * java.lang.Object)
     */
    @Override
    public int update(String sqlId, Object paramObj) {
        try {
            return sqlSession.update(getSqlId(paramObj, sqlId), paramObj);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("更新失败:sqlId: " + sqlId + " , paramObj: " + paramObj, e);
        }
    }

    /**
     * 对{@link org.apache.ibatis.session.SqlSession#update(java.lang.String)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId 映射的语句ID
     *              参数
     * @return 执行结果——更新成功的记录数
     * @see org.apache.ibatis.session.SqlSession#update(java.lang.String)
     */
    @Override
    public int update(String sqlId) {
        try {
            return sqlSession.update(getSqlId(null, sqlId));
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("添加失败:sqlId: " + sqlId, e);
        }
    }

    @Override
    public int update(T t) {
        return update(CrudSqlId.update.name());
    }

    /**
     * 对
     * {@link org.apache.ibatis.session.SqlSession#delete(java.lang.String, java.lang.Object)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId    映射的语句ID
     * @param paramObj 参数
     * @return 执行结果——删除成功的记录数
     * @see org.apache.ibatis.session.SqlSession#delete(java.lang.String,
     * java.lang.Object)
     */
    @Override
    public int delete(String sqlId, Object paramObj) {
        try {
            return sqlSession.delete(getSqlId(paramObj, sqlId), paramObj);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("删除失败:sqlId: " + sqlId + " , paramObj: " + paramObj, e);
        }
    }

    /**
     * 对{@link org.apache.ibatis.session.SqlSession#delete(java.lang.String)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId 映射的语句ID
     * @return 执行结果——删除成功的记录数
     * @see org.apache.ibatis.session.SqlSession#delete(java.lang.String)
     */
    @Override
    public int delete(String sqlId) {
        try {
            return sqlSession.delete(getSqlId(null, sqlId));
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("删除失败:sqlId: " + sqlId, e);
        }
    }

    /**
     * 对
     * {@link org.apache.ibatis.session.SqlSession#selectList(java.lang.String, java.lang.Object)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId    映射的语句ID
     * @param paramObj 参数
     * @return 查询结果列表
     * @see org.apache.ibatis.session.SqlSession#selectList(java.lang.String,
     * java.lang.Object)
     */
    @Override
    public <E> List<E> selectList(String sqlId, Object paramObj) {
        try {
            return sqlSession.selectList(getSqlId(paramObj, sqlId), paramObj);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:sqlId: " + sqlId + " , paramObj: " + paramObj, e);
        }
    }

    // @Override
    @Deprecated
    public <E> List<E> selectListByExample(String sqlId, MyBatisSupportEntity abstractEntity) {
        try {
            // 以下为map方式 生成动态语句
            // Map<String, Object> map2=MyBatisDaoUtil.getKeyValuesMap(abstractEntity);
            // map2.put(DaoConstant.keyValuesMapKey,
            // MyBatisDaoUtil.getsqlIdKeyValuesMap(abstractEntity));
            // return sqlSession
            // .selectList(getSqlName(sqlId), map2);
            // 以下为以list方式 生成动态语句的
            MyBatisDaoUtil.setMyBatisType(abstractEntity);
            return sqlSession.selectList(getSqlId(abstractEntity, sqlId), abstractEntity);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException | IntrospectionException | NoSuchMethodException | UnsupportDataTypeException e) {
            //e.printStackTrace();
        }
        return null;
    }

    /**
     * 对
     * {@link org.apache.ibatis.session.SqlSession#selectList(java.lang.String)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId 映射的语句ID
     * @return 查询结果列表
     * @see org.apache.ibatis.session.SqlSession#selectList(java.lang.String)
     */
    @Override
    public <E> List<E> selectList(String sqlId) {
        try {
            return sqlSession.selectList(getSqlId(null, sqlId));
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:sqlId: " + sqlId, e);
        }
    }

    /**
     * 对
     * {@link org.apache.ibatis.session.SqlSession#selectList(java.lang.String)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId 映射的语句ID
     * @return 查询结果列表
     * @see org.apache.ibatis.session.SqlSession#selectList(java.lang.String)
     */
    @Override
    public List<? extends Map<String, ?>> selectListForMap(String sqlId) {
        try {
            return sqlSession.selectList(getSqlId(null, sqlId));
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:sqlId: " + sqlId, e);
        }
    }

    /**
     * 对
     * {@link org.apache.ibatis.session.SqlSession#selectOne(java.lang.String, java.lang.Object)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId    映射的语句ID
     * @param paramObj 参数
     * @return 查询结果对象
     * @see org.apache.ibatis.session.SqlSession#selectOne(java.lang.String,
     * java.lang.Object)
     */
    @Override
    public <E> E selectOne(String sqlId, Object paramObj) {
        try {
            return sqlSession.selectOne(getSqlId(paramObj, sqlId), paramObj);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:sqlId: " + sqlId + " , paramObj: " + paramObj, e);
        }
    }

    /**
     * 对{@link org.apache.ibatis.session.SqlSession#selectOne(java.lang.String)}
     * 的代理。 将sqlId包装了命名空间，方便DAO子类调用。
     *
     * @param sqlId 映射的语句ID
     * @return 查询结果对象
     * @see org.apache.ibatis.session.SqlSession#selectOne(java.lang.String)
     */
    @Override
    public <E> E selectOne(String sqlId) {
        try {
            return sqlSession.selectOne(getSqlId(null, sqlId));
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:sqlId: " + sqlId, e);
        }
    }


    @Override
    public <E> List<E> selectAll() {
        try {
            T t = clazz.newInstance();
            return sqlSession.selectList(getSqlId(t, CrudSqlId.selectAll.name()), t);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:sqlId: " + CrudSqlId.selectAll.name(), e);
        }
    }

    @Override
    public <E> List<E> selectAll(T t) {
        try {
            return sqlSession.selectList(getSqlId(t, CrudSqlId.selectAll.name()), t);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("查询失败:sqlId: " + CrudSqlId.selectAll.name(), e);
        }
    }

    /**
     * 更新（持久化）对象.
     *
     * @param t               更新该参数有值的字段
     * @param toNullColumnArr 在使用有值字段更新时，如有 要清空的字段，通过此参数传来
     * @return
     */
    @Override
    public Integer updateSelectedColumn(T t, String... toNullColumnArr) {
        try {
            if (null != toNullColumnArr) {
                // MyBatisDaoUtil.setMyBatisType(t);
                FullMatchedDbColumn fullMatchedDbColumn = t.getClass().getAnnotation(FullMatchedDbColumn.class);
                List<String> toNullColumnList = new ArrayList<>(toNullColumnArr.length);
                for (String toNullColumn : toNullColumnArr) {
                    toNullColumnList.add(MyBatisDaoUtil.getColumnName(toNullColumn, fullMatchedDbColumn));
                }
                PrivateMethodUtil.invokeMethod(t, "setToNullColumnList", toNullColumnList);
            }
            setPkName(t);
            return sqlSession.update(getSqlId(t, CrudSqlId.updateSelectedColumnById.name()), t);
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DAOException("更新失败:t: " + t, e);
        }
    }

//    public void setSelf(Object proxyService) {
//        self = proxyService;
//    }
}
